import moment = require('moment');
import crypto = require('crypto');
import {Promise} from 'es6-promise';
import {Response} from "express";

import fs = require('fs');

import { Magics } from "./magics";
import { ObjectID } from "mongodb";
import { StrOptType, UIDType, PayType, TransType } from "./defines";

// export external moudle utilities
export let Date = moment;

// export common constant varibles
export let regExpEmptyStr       = /^\s*$/;
export let regExpPPE            = /PPE|ppe|皮皮易/;
export let regExpNumeric        = /^[-]?\d*\.?\d*$/;
export let regExpPositiveNum    = /^\d*\.?\d*$/;
export let regExpEscChar        = /([\?,\*,\(,\),\[,\],\\,\ ])/g;
export let regExpImageFile      = /.*\.(gif)|(jpeg)|(jpg)|(png)$/;
export let regExpChineseChar    = /[\u4e00-\u9fa5\uf900-\ufa2d]/;
export let regExpAllChineseChar = /^[\u4e00-\u9fa5\uf900-\ufa2d]+$/;
export let regExpEmail          = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/igm;
export let regExpUrl            = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
export let regExpIPAddr         = /^(([01]?[\d]{1,2})|(2[0-4][\d])|(25[0-5]))(\.(([01]?[\d]{1,2})|(2[0-4][\d])|(25[0-5]))){3}$/;
export let regExpTelNum         = /^([0-9]{3,4}-)?[0-9]{7,8}$/;
export let regExpMobileNum      = /^((\+?86)|(\(\+86\)))?(13[0-9]{9}|15[0-9]{9}|18[0-9]{9}|147[0-9]{8}|134[0-9]{8}|17[0143756][0-9]{8})$/;
export let regExpChineseIdNum   = /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
export let regExpSysUserEmail   = /&?@xianppe.com/;

export let regExpPPEId      = /^[0-9A-Z]{6}$/i;
export let regExpMaterialId = /^[0-9A-Z]{8}$/i;
export let regExpTransId      = /^[0-9a-f]{24}[0-9]{2}$/i;
export let regExpPassWdToken  = /^[0-9a-f]{32}$/i;
export let regExpShopId      = /^DP[0-9A-Z]{6}$/i;

// --------------------- Strong Password ------------------------------
//  1. Passwords will contain at least 1 upper case letter
//  2. Passwords will contain at least 1 lower case letter
//  3. Passwords will contain at least 1 number or special character
//  4. Passwords will contain at least 8 characters in length
//  5. Password maximum length should not be arbitrarily limited
// --------------------- Strong Password ------------------------------
export let regExpPassword = /(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/;

// export customized validators
export let isArray = Array.isArray;
export function nop(): void {
}   // this function is designed to do nothing

export function isError(val: any): boolean {
    return !!val && typeof val === 'object' && objectToString.call(val) === '[object Error]';
}

export function isDate(obj: any) {
    return obj instanceof Date;
}

export function isNull(obj: any): boolean {
    return obj === null;
}

export function isNotNull(obj: any): boolean {
    return obj !== null;
}

export function isDefined(obj: any): boolean {
    return obj !== null && obj !== undefined;
}

export function isUndefined(obj: any): boolean {
    return typeof obj === 'undefined';
}

export function isInt(num: string|number): boolean {
    return parseInt(num + '') + '' === num;
}

export function isObject(obj: any) {
    return !!obj && (typeof obj === 'object' || typeof obj === 'function');
}

export function isNumber(val: any) {
    return typeof val === 'number' && !isNaN(val);
}

export function isNumeric(str: string|number): boolean {
    return regExpNumeric.test(str + '');
}

export function isPositiveNum(str: string|number): boolean {
    return regExpPositiveNum.test(str + '');
}

export function isString(val: any) {
    return typeof val === 'string';
}

export function isRegExp(val: any) {
    return isObject(val) && objectToString.call(val) == '[object RegExp]';
}

export function isPromise(p: any) {
    return !!p && isFunction(p.then);
}

export function isBoolean(val: any) {
    return typeof val === 'boolean';
}

export function isInteger(val: any) {
    return isNumber(val) && val % 1 === 0;
}

export function isFunction(val: any) {
    return typeof val === 'function';
}

export function hasChineseChar(str: string): boolean {
    return regExpChineseChar.test(str);
}

export function isIP(str: string) {
    return regExpIPAddr.test(str);
}

export function isUrl(str: string): boolean {
    return regExpUrl.test(str.toLowerCase());
}

export function isEmail(str: string): boolean {
    return regExpEmail.test(str);
}

export function isPPEid(str: string): boolean {
    return regExpPPEId.test(str);
}

export function isShopId(str: string): boolean {
    return regExpShopId.test(str);
}

export function isMaterialId(str: string): boolean {
    return regExpMaterialId.test(str);
}

export function isTele(str: string): boolean {
    return regExpTelNum.test(str);
}

export function isPhone(str: string): boolean {
    return regExpMobileNum.test(str);
}

export function isObjectId(item: string|number): boolean {
    return ObjectID.isValid(item);
}

export function isImageFile(str: string): boolean {
    return regExpImageFile.test(str);
}

export function isAllChinessChar(str: string): boolean {
    return regExpAllChineseChar.test(str);
}

export function isStrongPassword(str: string): boolean {
    return regExpPassword.test(str);
}

export function isChineseIdCardNum(str: string): boolean {
    if( !regExpChineseIdNum.test(str) )
        return false;

    let sum    = 0;
    let vcode  = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2];
    let weight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];

    str = str.toUpperCase();
    for( let i = 0; i < 17; ++i ) {
        sum += toInt(str[i]) * weight[i];
    }

    let mod = sum % 11;
    return mod === 2 && str[17] === 'X' || toInt(str[17]) === vcode[mod];
}

export function isBankCardNum(str: string): boolean {
    let lastNum = toInt(str.substr(str.length - 1, 1));

    let numArr = str.split('').reverse();
    let total  = 0;
    for( let j = 1; j < numArr.length; ++j ) {
        if( j % 2 == 1 ) {
            let temp = toInt(numArr[j]) * 2;
            if( temp < 9 ) {
                total += temp;
            } else {
                total += temp % 10;
                total += toInt(temp / 10);
            }
        } else {
            total += toInt(numArr[j]);
        }
    }
    total %= 10;

    return (10 - (total == 0 ? 10 : total)) == lastNum;
}

export function isTransId(str: string): boolean {
    return regExpTransId.test(str + '');
}

export function isPassWdToken(str: string): boolean {
    return regExpPassWdToken.test(str + '');
}

export function isEmpty(val: any) {
    if( !isDefined(val) ) {
        return true;
    }

    if( isFunction(val) ) {
        return false;
    }

    if( isString(val) ) {
        return regExpEmptyStr.test(val);
    }

    if( isArray(val) ) {
        return val.length === 0;
    }

    if( isDate(val) ) {
        return false;
    }

    if( isObject(val) ) {
        for( let attr in val ) {
            return false;
        }
        return true;
    }
    return false;
}

export function isNotEmpty(obj: any): boolean {
    return !isEmpty(obj);
}

export function clearEmpty(obj: any): Object {
    if( !obj ) {
        return obj;
    }

    if( isArray(obj) ) {
        return obj['filter']((e) => isDefined(e));
    }

    if( isObject(obj) ) {
        let ret = {};
        for( let k in obj ) {
            if( isDefined(obj[k]) ) {
                ret[k] = obj[k];
            }
        }
        return ret;
    }

    return obj;
}

export function clearInvalidVal(obj: any): Object {
    if (!obj) {
        return obj;
    }

    let ret = {};
    for (let k in obj) {
        var val = obj[k];
        if (isDefined(val) && ''!==val) {
            ret[k] = obj[k];
        }
    }
    return ret;
}

// export customized functions
export let objectToString = Object.prototype.toString;

export function toInt(num: string|number): number {
    return parseInt(num + '');
}

export function ceil(num: number): number {
    return Math.ceil(num);
}

export function toPrice(num: string|number, degree?: number): number|string {
    if( !isNumeric(num) ) {
        return '';
    }
    let ret;
    let strNum = num + '';
    if( -1 != strNum.indexOf('.') && (strNum.length - strNum.indexOf('.') - 1 >= 2) ) {
        ret = parseFloat(parseFloat(strNum).toFixed(degree || 2));
    } else {
        ret = parseFloat(strNum);
    }
    return ret;
}

export function toStrDateTime(time: string|Date): string {
    return moment(time).format('YY-MM-DD HH:mm:ss');
}

export function toStrDate(time: string|Date): string {
    return moment(time).format('YYYY-MM-DD');
}

export function extend(obj: Object, ...objs: Array<Object>) {
    if( !isObject(obj) ) {
        return obj;
    }
    objs.forEach(function(source) {
        if( isObject(source) ) {
            for( let k in source ) {
                obj[k] = source[k]
            }
        }
    });
    return obj;
}

export function merge(obj: [Object]) {
    let result = {};
    obj.forEach(function(source) {
        for( let k in source ) {
            result[k] = source[k]
        }
    });

    return result;
}

export function pick(obj: Object, keys: any[]) {
    return keys.reduce(function(result, key) {
        if( key in obj ) {
            result[key] = obj[key];
        }
        return result;
    }, {});
}

export function regExpEscape(str) {
    return str.replace(regExpEscChar, '\\$1');
}

export function passwdHash(raw): string {
    return md5(md5(raw + Magics.PasswdSalt));
}

export function md5(text: string): string {
    return crypto.createHash('md5').update(text).digest('hex');
}

export function sha1(text: string): string {
    return crypto.createHash('sha1').update(text).digest('hex');
}

export function sha1WithRsa(text: string, rsa){
    var signer = crypto.createSign('RSA-SHA1');
    signer.update(new Buffer(text, 'utf-8'));
    try {
        var signed = signer.sign(rsa, 'base64');
    }catch (err){
        console.log(err)
    }

    return signed;
}

export function verifyRsa(text: string, rsa_public, sign){
    var verify = crypto.createVerify('RSA-SHA1');
    verify.update(new Buffer(text, 'utf-8'));
    return verify.verify(rsa_public, sign, 'base64');
}

export function objectId(): ObjectID {
    return new ObjectID();
}

export function toObjectId(str: string|number): ObjectID {
    return ObjectID.isValid(str) && new ObjectID(str) || null;
}

export function enumToStrMap(types: any, option: StrOptType = StrOptType.NORMAL): Object {
    let val: string;
    let map: any = {};
    for( let k in types ) {
        if( typeof types[k] === 'number' ) {
            switch( option ) {
                case StrOptType.UPPER_CASE:
                    val = k.toLocaleUpperCase();
                    break;
                case StrOptType.LOWER_CASE:
                    val = k.toLocaleLowerCase();
                    break;
                default:
                    val = k;
            }
            map[k] = val;
        }
    }
    return map;
}

export function enumToStrArray(types: any, option: StrOptType = StrOptType.NORMAL): string[] {
    let names: string[] = [];
    for( let n in types ) {
        if( typeof types[n] === 'number' ) {
            switch( option ) {
                case StrOptType.UPPER_CASE:
                    n = n.toLocaleUpperCase();
                    break;
                case StrOptType.LOWER_CASE:
                    n = n.toLocaleLowerCase();
                    break;
            }
            names.push(n);
        }
    }
    return names;
}

export function toDeliveryAddr(loc: Object, address: string): string {
    let city = ('市辖区' == loc['city']) || ('县' == loc['city']) ? '' : loc['city'];
    let dist = ('市辖区' == loc['dist']) || ('县' == loc['dist']) ? '' : loc['dist'];
    return loc['prov'] + city + dist + address;
}

export function encodeId(id: ObjectID|string): string {
    let str: string[]  = id.toString().split('');
    let first: string  = str.splice(0, 5).reverse().join('');
    let second: string = str.splice(0, 10).reverse().join('');
    let third: string  = str.reverse().join('');
    return second + first + third;
}

export function decodeId(id: ObjectID|string): string {
    let str: string[]  = id.toString().split('');
    let first: string  = str.splice(0, 10).reverse().join('');
    let second: string = str.splice(0, 5).reverse().join('');
    let third: string  = str.reverse().join('');
    return second + first + third;
}

export function isBelongToObj(value: number | string, option: Object): boolean{
    let result = false;
    for(let key in option){
        if(value == key){
            result = true;
            break;
        }
    }
    return result;
}

export function ipToLong(ip: string): number {
    let val = 0;
    ip.split('.').forEach(function(octet) {
        val <<= 8;
        val += parseInt(octet);
    });
    return (val >>> 0);
}

export function isAjaxReq(res: Response): boolean {
    if (res['getHeader']('x-requested-with') != null &&
        res['getHeader']('x-requested-with')['equalsIgnoreCase']('XMLHttpRequest')) {
        return true;
    }
    return false;
}

export function longToIp(val: number): string {
    return ((val >>> 24) + '.' +
            (val >> 16 & 255) + '.' +
            (val >> 8 & 255) + '.' +
            (val & 255) );
}

export function toDateString(time: Date): string {
    return Date(time).format("YYYY-MM-DD");
}

let digits = [
    '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'A', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
    'M', 'N', 'P', 'R', 'T', 'V', 'W', 'X', 'Y'
];
export function vendorCode(phone: string) {
    let val: number = toInt(phone) - 10020151012;
    let len: number = digits.length;
    let div: number = toInt(val / len);
    let str: string = '';
    while( div > 0 ) {
        str += digits[val % len];
        val = toInt(val / len);
        div = toInt(val / len);
    }

    while( str.length < 6 ) {
        str += digits[toInt(Math.random() * len)];
    }

    return str.substring(4, 6) + str.substring(0, 2) + str.substring(2, 4);
}

export function tableIndex(actTab: Object, tableName: string, menuName: string): number {
    let index: number = -1;
    for( let key in actTab ) {
        if( key != tableName ) {
            continue;
        }
        if( actTab[key] && actTab[key].length ) {
            let menu = actTab[key];
            for( let e of menu ) {
                ++index;
                if( menuName == e.route.replace(tableName + '_', '') ) {
                    return index;
                }
            }
        }
    }
    return index;
}

export function accessId(access: string): string {
    //hc360@xianppe => 11c11663ae4657
    //mjh1688@xianppe => 3aecde33598e53
    return md5(access).substr(7, 14);
}

export function getObjValidKeys(obj: any): Object {
    for( let k in obj ) {
        if( !isDefined(obj[k]) || obj[k] === '' ) {
            delete obj[k];
        }
    }
    return obj;
}

export function padZero(num: number, n: number): string {
    var len    = num.toString().length;
    let result = num.toString();
    while( len < n ) {
        result = '0' + result;
        ++len;
    }
    return result;
}

export function minInArray(list: Array<any>, iterate ?: Function){
    if(list.length == 0 || (typeof list[0] == 'object' && (typeof iterate != 'function'))){
        return null;
    }
    let first = list[0];
    if(iterate && typeof iterate == 'function'){
        list.forEach(function (item) {
            if( iterate(item) < iterate(first)){
                first = item;
            }
        });
    } else {
        list.forEach(function (item) {
            if( item < first){
                first = item;
            }
        });
    }

    return first;
}

export function maxInArray(list: Array<any>, iterate ?: Function){
    if(list.length == 0 || (typeof list[0] == 'object' && (typeof iterate != 'function'))){
        return null;
    }
    let first = list[0];
    if(iterate && typeof iterate == 'function'){
        list.forEach(function (item) {
            if( iterate(item) > iterate(first)){
                first = item;
            }
        });
    } else {
        list.forEach(function (item) {
            if( item > first){
                first = item;
            }
        });
    }

    return first;
}

let charmap     = [
    '6', 'L', 'T', 'P', 'W', 'X', 'Y', 'I', 'K',
    '7', 'H', 'O', '4', 'Q', 'S', 'B', 'G', 'A',
    '2', 'Z', '0', 'C', '8', '1', 'V', 'E', 'J',
    'N', 'F', '3', '9', 'D', 'M', 'U', '5', 'R'
];
var uniqueIdCfg = [
    // 2^(bits + 1) > 36^len > 2^bits = 2^(digits * 4 - n), mask = 2^bits -1;
    { len: 6, bits: 31, digits: 8, mask: 0x7fffffff },
    { len: 7, bits: 36, digits: 9, mask: 0xfffffffff },
    { len: 8, bits: 41, digits: 11, mask: 0x1ffffffffff }
];
export function uniqueId(str: string = '', type: UIDType = UIDType.BIT6): string {
    let num: number;
    let mod: number;
    let result: string = '';
    let base: number   = charmap.length;
    let cfg            = uniqueIdCfg[type > uniqueIdCfg.length ? 0 : type];

    str = str || objectId().toString();
    num = Math.abs(Number('0x' + md5(str).substr(7, cfg.digits)) & cfg.mask);

    while( num ) {
        mod = num % base;
        result += charmap[mod];
        num = toInt(num / base);
    }

    if( result.length < cfg.len ) {
        let pads = cfg.len - result.length;
        for( let i = 0; i < pads; ++i ) {
            result += charmap[i];
        }
    }
    return result;
}

export function orderNo(): string {
    const rNum = 4;
    let randomSeed = objectId().toString();
    let len = randomSeed.length;
    let strRandom: string = '';
    for(let i=len-1; i>=0; --i){
        let val = randomSeed[i];
        if(isNumeric(val)) strRandom += val;
        if(rNum === strRandom.length) break;
    }
    return Date().format("MMDDHHmm") + strRandom;
}

export function transNo(): string {
    return objectId().toString();
}